﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SmartRoute.Data;
using SmartRoute.Randomness;
using SmartRoute.Analytics;

namespace SmartRoute.Optimization
{
    //BAD IDEA!! Mutate cannot be typed properly like this.
    ///// <summary>
    ///// A representation of a solution which can be generated randomly or mutated.
    ///// All three members of this interface should be pure functions.
    ///// </summary>
    //public interface ISolution
    //{
    //    /// <summary>
    //    /// Construct a new solution at random.
    //    /// </summary>
    //    /// <param name="m">The source of randomness to use</param>
    //    /// <param name="school">The schools for which the result is a solution.</param>
    //    /// <returns>A solution generated at random.</returns>
    //    static ISolution Build(RandomSource m, IEnumerable<School> school);
    //    static ISolution Mutate(RandomSource m, ISolution toMutate);
    //    static IEnumerable<Bus> Solve(ISolution toSolve, IEnumerable<Bus> buses);
    //}

    //Typeclass alternative:
    public interface ISolution<T>
    {
        T Build(RandomSource m, IEnumerable<School> school);
        T Mutate(RandomSource m);
        IEnumerable<IEnumerable<Bus>> SolveFor(IEnumerable<Bus> buses);
    }

    //Typeclass alternative 2:
    public interface ISolution<T,R>
    {
        T Build(R m, IEnumerable<School> school);
        T Mutate(R m);
        IEnumerable<IEnumerable<Bus>> SolveFor(IEnumerable<Bus> buses);
    }

    public static class ISolutionFunctions
    {
        public static IEnumerable<IEnumerable<Bus>> SolveForNBuses<T>(IRoutingService routingService, Location depot, ISolution<T> toSolve, uint buses)
        {
            return toSolve.SolveFor(Bus.PutBusesAtDepot(routingService, depot, buses));
        }
        public static IEnumerable<Route> ToRoutes(IEnumerable<IEnumerable<Bus>> solution)
        {
            return from busSeries in solution
                   select busSeries.SelectMany(x => x.Directions).Aggregate((x, y) => x + y);
        }

        /// <summary>
        /// Sum the costs of the results to obtain total cost.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <typeparam name="U"></typeparam>
        /// <param name="numberOfBuses"></param>
        /// <param name="depot"></param>
        /// <param name="routeService"></param>
        /// <param name="fitnessFunction"></param>
        /// <param name="toTest"></param>
        /// <returns></returns>
        public static double TestWith<T, U>(uint numberOfBuses, Location depot, IRoutingService routeService, Analyzer<U> fitnessFunction, ISolution<T> toTest)
        {
            return SolveForNBuses(routeService,depot,toTest,numberOfBuses).Select(x => fitnessFunction.runTest(x)).Sum();
        }
    }
}
